package com.chinackts.common.okhttp;

import com.alibaba.fastjson.JSON;
import com.chinackts.auth.model.ProxyIP;
import com.chinackts.common.property.ProxyApiProperties;
import com.chinackts.common.property.ProxyApiSelectionProperties;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class ProxyWeightedSelector {

    private static final String WEIGHT_COUNTER_PREFIX = "e-port:proxy:api:counter:";
    private static final String PROXY_CACHE_PREFIX = "e-port:proxy:cache:global";

    @Resource
    private StringRedisTemplate redisTemplate2;

    @Resource
    private RedissonClient redissonClient2;

    @Resource
    private ProxyApiProperties proxyApiProperties;

    /**
     * 获取带权重分配的代理API
     */
    public ProxyApiSelectionProperties selectApiByWeight() {
        List<ProxyApiSelectionProperties> selection = proxyApiProperties.getApiSelection();
        if (CollectionUtils.isEmpty(selection)) return null;

        // 获取分布式计数器
        RAtomicLong totalCounter = redissonClient2.getAtomicLong(WEIGHT_COUNTER_PREFIX + "total");
        Map<String, RAtomicLong> apiCounters = new HashMap<>();
        selection.forEach(item ->
                apiCounters.put(item.getUrl(), redissonClient2.getAtomicLong(WEIGHT_COUNTER_PREFIX + item.getUrl())));

        // 使用分布式锁保证选择操作的原子性
        RLock lock = redissonClient2.getLock(WEIGHT_COUNTER_PREFIX + "lock");
        boolean isLocked = false;
        try {
            isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
            if (!isLocked) {
                // 获取锁失败时，返回默认API
                return selection.iterator().next();
            }
            double totalWeight = getTotalWeight(selection);
            long total = totalCounter.get();
            ProxyApiSelectionProperties selected = selection.stream()
                    .min(Comparator.comparingDouble(entry -> {
                        double expectedRatio = entry.getWeight() / totalWeight;
                        double actualRatio = apiCounters.get(entry.getUrl()).get() / (double) Math.max(1, total);
                        return actualRatio - expectedRatio;
                    })).orElseGet(() -> selection.iterator().next());

            // 更新计数器
            totalCounter.incrementAndGet();
            apiCounters.get(selected.getUrl()).incrementAndGet();

            return selected;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢复中断状态
            // 线程中断时返回默认API
            return selection.iterator().next();
        } catch (Exception e) {
            // 其他异常情况下也返回默认API，确保能获取到API
            return selection.iterator().next();
        } finally {
            // 只有在成功获取锁的情况下才释放锁
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public ProxyIP getProxyIP() {
        ValueOperations<String, String> ops = this.redisTemplate2.opsForValue();
        String proxyIP = ops.get(PROXY_CACHE_PREFIX);
        if (StringUtils.isEmpty(proxyIP)) return null;
        return JSON.parseObject(proxyIP, ProxyIP.class);    }

    public void saveProxyIP(ProxyIP proxyIP) {
        ValueOperations<String, String> ops = this.redisTemplate2.opsForValue();
        ops.set(PROXY_CACHE_PREFIX, JSON.toJSONString(proxyIP),10, TimeUnit.MINUTES);
    }

    public void delProxyIP() {
        redisTemplate2.delete(PROXY_CACHE_PREFIX);
    }

    private int getTotalWeight(List<ProxyApiSelectionProperties> selection) {
        return selection.stream().map(ProxyApiSelectionProperties::getWeight).reduce(0, Integer::sum);
    }
}
